MyBatis 编写 Mapper
概述
参考资料 XML约束——DTD约束
使用了 MyBatis 之后就无需再像传统的方式那样去写 JDBC 实现类了,而是使用 ORM(对象关系映射)将 SQL 语句的结果封装成一个对象
直接通过 XML 的形式去添加一个 Mapper 文件
注意:一般这个 Mapper 映射文件的包和实体的包(Dao 层)是在一起的,所以在 resources 目录下创建一个同名的包,例如 com/alsritter/mapper
不要用 .
使用的是 /
,否则会把 .
当成字符名称创建一个包名
配置环境
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.4</version>
</dependency>
<!-- MySQL的驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.20</version>
</dependency>
<!-- 单元项目 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
<!-- 日志系统 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
编写 Mapper 的基本流程
创建一个 Dao 层的接口
定义一个 UserMapper 接口
public interface UserMapper {
List<User> getUserList();
}
编写对应的 Mapper 文件
小知识:这里的 XML 使用的是 DTD约束
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace去绑定一个对应的Dao/Mapper接口 -->
<mapper namespace="com.alsritter.dao.UserMapper">
<!-- 绑定方法,以及返回类的坐标(resultType) -->
<select id="getUserList" resultType="com.alsritter.pojo.User">
select * from users
</select>
</mapper>
namespace:namespace中的包名要和 Dao/Mapper 接口中的包名一致
id:就是对应的namespace的方法名
resultType:就是SQL语句执行的返回值,增删改使用 int 当作返回值(修改条目)
添加映射
在核心配置文件 (mybatis-config.xml)
里添加上这个映射
<mappers>
<mapper resource="com\alsritter\dao\UserMapper.xml"/>
</mappers>
使用上面写的 Mapper 查询数据
// 加载核心配置文件
InputStream inputStream = Resources.getResourceAsStream("UserMapper.xml");
// 获取 sqlSession 工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 获取 sqlSession 对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 执行 sql 语句
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUserList();
代理开发的方式
以前写好 Dao 层接口之后还需要手动再创建一个实现类去实现那个接口,但是现在使用 MyBatis 之后可以使用其动态代理对象来动态创建一个实现类(现在开发的主流)
如下,使用 Mapper 的 getMapper 方法获取一个实现类(动态代理模式)
// 执行 sql 语句
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUserList();
使用例就看上面的 “编写 Mapper 的基本流程”
不使用接口的方式
上面编写接口的步骤其实可以省略,完全只使用 Mapper 文件也可以(其实如果使用接口文件则是偏向注解开发的),但是一般都不使用这种无接口的方式,因为写接口的目的就是为了分离出 Dao 层,如果直接通过下面这种指定的方式写会造成代码耦合度很高
下面演示一下不用接口完全使用 XML 文件的方式编写一个查询方法
一样,先写个 Mapper 文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 这里随便写一个不存在的命名空间 -->
<mapper namespace="userMapper">
<!-- 绑定方法,以及返回类的坐标(resultType) -->
<select id="getUserList" resultType="com.alsritter.pojo.User">
select * from users
</select>
<select id="getUserById" parameterType="int" resultType="com.alsritter.pojo.User">
select * from users where id = #{id};
</select>
<insert id="addUser" parameterType="com.alsritter.pojo.User">
insert into studyjdbc.users (name ,pwd) values (#{name},#{pwd});
</insert>
<update id="updateUser" parameterType="com.alsritter.pojo.User">
update studyjdbc.users set name = #{name},pwd = #{pwd} where id = #{id};
</update>
<delete id="deleteUser" parameterType="int">
delete from studyjdbc.users where id = #{id};
</delete>
</mapper>
使用上面写的 Mapper 查询数据
// 加载核心配置文件
InputStream inputStream = Resources.getResourceAsStream("UserMapper.xml");
// 获取 sqlSession 工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 获取 sqlSession 对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 直接通过在 XML 文件写的命名空间和 id 来读取数据
// 1、查询数据
List<User> userList = sqlSession.selectList("userMapper.getUserList");
// 2、添加数据
sqlSession.insert("userMapper.addUser", user);
// 3、修改数据
sqlSession.update("userMapper.updateUser", user);
// 4、删除数据
sqlSession.delete("userMapper.deleteUser", id);
基本的 CRUD 实例
构建数据元
@Data@ToString@AllArgsConstructor@NoArgsConstructor
public class User {
private int id;
private String name;
private String pwd;
}
编写 Mapper 接口
public interface UserMapper {
List<User> getUserList();
//根据id查询用户
User getUserById(int id);
//insert一个用户,注意,增删改需要提交事务
int addUser(User user);
//update一个用户
int updateUser(User newUser);
//delete一个用户
int deleteUser(int id);
}
编写 Mapper 映射
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace去绑定一个对应的Dao/Mapper接口 -->
<mapper namespace="com.alsritter.dao.UserMapper">
<select id="getUserList" resultType="com.alsritter.pojo.User">
select * from users;
</select>
<select id="getUserById" parameterType="int" resultType="com.alsritter.pojo.User">
select * from users where id = #{id};
</select>
<insert id="addUser" parameterType="com.alsritter.pojo.User">
insert into studyjdbc.users (name ,pwd) values (#{name},#{pwd});
</insert>
<update id="updateUser" parameterType="com.alsritter.pojo.User">
update studyjdbc.users set name = #{name},pwd = #{pwd} where id = #{id};
</update>
<delete id="deleteUser" parameterType="int">
delete from studyjdbc.users where id = #{id};
</delete>
</mapper>
编写测试类
public class UserMapperTest {
@Test
public void test() {
// 第一步:获取SqlSession对象
try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
// 执行SQl
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUserList();
for (User user : userList) {
System.out.println(user);
}
}
}
@Test
public void getUserById() {
try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
// 执行SQl
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
System.out.println(mapper.getUserById(1));
}
}
@Test
public void addUser() {
try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
// 执行SQl
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
System.out.println(mapper.addUser(new User(6,"小爱","2131270")));
// 如果要执行需要更新的操作,一般需要手动提交事务
sqlSession.commit();
}
}
@Test
public void updateUser(){
try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
// 执行SQl
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
System.out.println(mapper.updateUser(new User(6,"Lisa","1313270")));
sqlSession.commit();
}
}
@Test
public void deleteUser(){
try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
// 执行SQl
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
System.out.println(mapper.deleteUser(1));
sqlSession.commit();
}
}
}
插入数据类型
通过标签的 parameterType 属性来指定要插入的数据类型
<select id="getUserById" parameterType="int" resultType="com.alsritter.pojo.User">
select * from users where id = #{id};
</select>
<insert id="addUser" parameterType="com.alsritter.pojo.User">
insert into studyjdbc.users (name ,pwd) values (#{name},#{pwd});
</insert>
插入的数据也必须和上面的参数一致
@Test
public void getUserById() {
try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
// 执行SQl
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
System.out.println(mapper.getUserById(1));
}
}
@Test
public void addUser() {
try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
// 执行SQl
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
System.out.println(mapper.addUser(new User(6,"小爱","2131270")));
// 如果要执行需要更新的操作,一般需要手动提交事务
sqlSession.commit();
}
}
传递参数给 SQL 语句
有两种方式传参
#{name}
和${name}
使用${}
方式传入的参数,mybatis不会对它进行特殊处理,而使用#{}
传进来的参数,mybatis默认会将其当成字符串。
#{name}
:预编译的形式,可以防止sql注入
${name}
:不带预编译,直接通过字符拼接的形式
使用 Map 来填入参数
前面使用 User 当作参数传进去相对而言没有那么自由 因为必须使用 POJO 里相同的参数名来传 而使用 Map 之后,可以直接使用 Key 名来取值
<insert id="addUser02" parameterType="map">
insert into studyjdbc.users (name ,pwd) values (#{userName},#{password});
</insert>
// 使用Map来填入参数
int addUser02(Map<String,Object> map);
@Test
public void addUser02(){
try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
// 执行SQl
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String,Object> map = new HashMap<>();
map.put("userName","小王");
map.put("password","1214214145");
System.out.println(mapper.addUser02(map));
sqlSession.commit();
}
}
模糊查询
但是注意,这样写可能会存在 SQL注入 的问题
//模糊查询
List<User> getUserByLikeList(String value);
<select id="getUserByLikeList" parameterType="String" resultType="com.alsritter.pojo.User">
select * from users where name like #{value};
</select>
@Test
public void getUserByLikeList() {
try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
// 执行SQl
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUserByLikeList("李%");
for (User user : userList) {
System.out.println(user);
}
}
}
提交事务
前面的操作后面都加了个 sqlSession.commit();
手动提交
MyBatis 默认是关闭自动提交的,如果要打开则把 前面写好的工具类
openSession
传递一个Boolean变量
其函数原型为
SqlSession openSession(boolean autoCommit);
所以传入一个true就可以打开自动提交了
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession(true);
}
不过,最好还是手动提交,防止一些错误信息也给提交上去了
分页查询
SQL里的limit
关键字
-- 例
-- 从0开始分页 2个为一页(注意,前闭后开)
SELECT * FROM user limit 0,2;
传统使用SQL的方式
一般使用这种方式就行了
<select id="getUsersByLimit" parameterType="map" resultType="com.alsritter.pojo.User">
select * from users limit #{startIndex} , #{pageSize};
</select>
接口
List<User> getUsersByLimit(Map<String,Integer> map);
测试
@Test
public void test(){
try(SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap<String, Integer> map = new HashMap<>();
map.put("startIndex",0);
map.put("pageSize",3);
List<User> usersByLimit = mapper.getUsersByLimit(map);
usersByLimit.forEach((i)->{
logger.info(i);
});
}
}
RowBounds 来分页
不推荐这种方式
<select id="getUserByBounds" resultType="com.alsritter.pojo.User">
select * from users;
</select>
接口
// 使用RowBounds来分页
List<User> getUserByBounds();
@Test
public void test(){
try(SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
// UserMapper mapper = sqlSession.getMapper(UserMapper.class); 直接通过会话来调用方法
List<User> usersByLimit =sqlSession.selectList("com.alsritter.dao.UserMapper.getUserByBounds"null,new RowBounds(0,2));
usersByLimit.forEach((i)->{
logger.info(i);
});
}
}
使用注解开发
注意!!使用 class 读取不要在路径下同时使用注解包和xml,不然会报重复映射的错误
常用注解
常用的注解 注解名 | 作用 ----|--- @Select | 选择语句 @Insert | 插入 @Update | 更新 @Delete | 删除 @Param | 传递参数 @Result | 实现结果集封装 参考上面那个 resultMap 高级结果集映射 @Results | 封装多个结果集 (配合 @Result 使用) @One | 实现一对一结果集封装 @Many | 实现一对多结果集封装
使用例
直接在接口上加上注解
public interface UserMapper {
@Select("select * from users;")
List<User> getUserList();
}
然后把原本配置文件上的Mapper映射xml改成映射类
原:
<mappers>
<mapper resource="com\alsritter\dao\UserMapper.xml"/>
</mappers>
新:
<mappers>
<mapper class="com.alsritter.dao.UserMapper"/>
</mappers>
下面同理
传递参数
使用 @Param
注解
@Select("select * from users where id = #{id};")
User getUserById(@Param("id") int id);
@Param("id")
里的参数名与sql语句里的一致,形参名可以不同
注意:这个 @Param("name")
是可以使用引用类型 .
出属性的
如下所示:
@Insert("insert into users(name, pwd) VALUE(#{user.name},#{user.pwd})")
int addUser(@Param("user")User user);
结果集映射
注解@Results
和@Result
来结果集映射
<mapper namespace="data.UserMapper">
<resultMap type="data.User" id="userResultMap">
<!-- 用id属性来映射主键字段 -->
<id property="id" column="user_id"/>
<!-- 用result属性来映射非主键字段 -->
<result property="userName" column="user_name"/>
</resultMap>
</mapper>
使用注解的形式
@Select("select * from t_user where user_name = #{userName}")
@Results(
@Result(property = "userId", column = "user_id"),
@Result(property = "userName", column = "user_name")
)
User getUserByName(@Param("userName") String userName);